package jp.co.ricoh.melody.databases;

import android.util.Log;

import java.io.IOException;
import java.util.List;

import jp.co.ricoh.melody.models.AlarmPrefs;
import jp.co.ricoh.melody.models.CheckFirstAddPrefs;
import jp.co.ricoh.melody.models.entities.AlarmData;
import jp.co.ricoh.melody.utils.ConstantUtils;
import jp.co.ricoh.melody.utils.LogUtils;
import jp.co.ricoh.sdk.SdkConstant;
import jp.co.ricoh.sdk.storage.OnPersistResultListener;
import jp.co.ricoh.sdk.storage.StorageFile;
import jp.co.ricoh.sdk.storage.StorageRepository;
import jp.co.ricoh.sdk.utils.SystemControllers;

/**
 * Copyright (C) 2016 RICOH Co.,LTD.
 * All rights reserved.
 * <p>
 * This class is used to interactive with data that get from HDD.
 */
public class DBManager {
    /**
     * This interface is used to notify when data has changes.
     */
    public interface onDataChangedListener {
        void onAlarmAdded(int alarmId);

        void onAlarmChanged(int alarmId);

        void onAlarmRemoved(int alarmId);
    }

    public interface OnSaveDataResultListener {
        void onSuccess();

        void onError(int errorCode);
    }

    public interface OnDataLoadedListener {
        void onDataLoaded(boolean isHddReady);
    }

    private static final String TAG = DBManager.class.getSimpleName();
    private static volatile DBManager sDBManager;

    private AlarmPrefs mAlarmPrefs;
    private CheckFirstAddPrefs mCheckFirstAddPrefs;

    private StorageRepository mStorageRepository;
    private SystemControllers mSystemControllers;
    private onDataChangedListener mOnDataChangedListener;

    private boolean mIsDataLoaded;

    public static synchronized void newInstance(SystemControllers systemController) {
        if (sDBManager == null) {
            sDBManager = new DBManager(systemController);
        }
    }

    /**
     * DBManager singleton.
     *
     * @return
     */
    public static DBManager getInstance() {
        if (sDBManager == null) {
            sDBManager = new DBManager();
        }
        return sDBManager;
    }

    /**
     * Empty DBManager constructor.
     */
    public DBManager() {
        mAlarmPrefs = new AlarmPrefs();
        mCheckFirstAddPrefs = new CheckFirstAddPrefs();
    }

    /**
     * DBManager constructor.
     */
    public DBManager(SystemControllers systemController) {
        if (systemController == null) {
            throw new IllegalArgumentException();
        }
        mSystemControllers = systemController;
        mAlarmPrefs = new AlarmPrefs();
        mCheckFirstAddPrefs = new CheckFirstAddPrefs();
    }

    private boolean isHddAvailabilityChanged() {
        int hddAvailability = mSystemControllers.getSystemState(SdkConstant.HDD_AVAILABLE);
        return hddAvailability != SdkConstant.STATE_UNKNOWN;
    }

    /**
     * Check HDD ready when start app.
     *
     * @return
     */
    public boolean isDataLoaded() {
        return mIsDataLoaded;
    }

    public void loadData(final OnDataLoadedListener listener) {
        SystemControllers.OnStateChangeListener stateChangeListener = new SystemControllers.OnStateChangeListener() {

            @Override
            public void onStateChange() {
                if (isHddAvailabilityChanged()) {
                    mSystemControllers.detachStateChangeListener(this);
                    boolean isHddReady = loadFileStorage();
                    if (listener != null) {
                        listener.onDataLoaded(isHddReady);
                    }
                }
            }
        };
        mSystemControllers.attachStateChangeListener(stateChangeListener);
        mSystemControllers.requestStates();
    }

    private boolean loadFileStorage() {
        if (mIsDataLoaded) {
            return true;
        }
        mStorageRepository = mSystemControllers.getStorageRepository();
        boolean isHddReady = mStorageRepository.isHddReady();
        if (isHddReady) {
            mStorageRepository.put(ConstantUtils.CHECK_FIRST_ADD_PREFS_KEY, mCheckFirstAddPrefs);
            mStorageRepository.put(ConstantUtils.ALARM_PREFS_KEY, mAlarmPrefs);
            try {
                mStorageRepository.retrieve(ConstantUtils.CHECK_FIRST_ADD_PREFS_KEY);
                mStorageRepository.retrieve(ConstantUtils.ALARM_PREFS_KEY);
                if (mAlarmPrefs.getFileInstance() != null) {
                    mIsDataLoaded = true;
                }
            } catch (IOException e) {
                LogUtils.e(TAG, Log.getStackTraceString(e));
            }
        }
        boolean result = isHddReady && mIsDataLoaded;
        if (result) {
            LogUtils.d(TAG, "loadFileStorage => success");
        } else {
            LogUtils.d(TAG, "loadFileStorage => failure");
        }
        return result;
    }

    /**
     * This methos is used to check first clicked on button add.
     *
     * @return
     */
    public boolean isFirstAdd() {
        return mCheckFirstAddPrefs.isFirstAdd();
    }

    /**
     * This method is used to save state first clicked on button add.
     */
    public void setFirstAdd() {
        mCheckFirstAddPrefs.setFirstAdd(false);
        savePrefs(ConstantUtils.CHECK_FIRST_ADD_PREFS_KEY, mCheckFirstAddPrefs, new OnPersistResultListener() {
            @Override
            public void onSuccess() {
            }

            @Override
            public void onError(int i) {
                resetFirstAddValue();
            }
        });
    }

    /**
     * This method is used to reset value of parameter isFirstAdd when the application can not save data to HDD.
     */
    public void resetFirstAddValue() {
        mCheckFirstAddPrefs.setFirstAdd(true);
    }

    /**
     * This method is used to set listener for DBManager.
     *
     * @param onDataChangedListener
     */
    public void setOnDataChangedListener(onDataChangedListener onDataChangedListener) {
        mOnDataChangedListener = onDataChangedListener;
    }

    /**
     * This method is used to return list alarms which get from HDD.
     *
     * @return mListAlarmModels
     */
    public List<AlarmData> getListAlarm() {
        return mAlarmPrefs.getAlarmDataList();
    }

    /**
     * This method is used to get an melody by id from list alarms.
     *
     * @param alarmId The id of melody.
     * @return melody
     */
    public AlarmData getAlarmById(int alarmId) {
        AlarmData alarm = null;
        for (AlarmData alarmData : mAlarmPrefs.getAlarmDataList()) {
            if (alarmData != null) {
                if (alarmData.getId() == alarmId) {
                    alarm = alarmData;
                    break;
                }
            }
        }
        return alarm;
    }

    /**
     * This method is used to add new an melody to HDD.
     *
     * @param alarm                    The model is added to HDD.
     * @param onSaveDataResultListener The result listener when save data to hdd.
     */
    public void addAlarm(final AlarmData alarm, final OnSaveDataResultListener onSaveDataResultListener) {
        int id = 0;
        if (mAlarmPrefs != null
                && mAlarmPrefs.getAlarmDataList() != null
                && !mAlarmPrefs.getAlarmDataList().isEmpty()) {
            id = mAlarmPrefs.getAlarmDataList().get(0).getId() + 1;
        }
        alarm.setId(id);
        getListAlarm().add(0, alarm);
        savePrefs(ConstantUtils.ALARM_PREFS_KEY, mAlarmPrefs, new OnPersistResultListener() {
            @Override
            public void onSuccess() {
                if (mOnDataChangedListener != null) {
                    mOnDataChangedListener.onAlarmAdded(alarm.getId());
                }
                if (onSaveDataResultListener != null) {
                    onSaveDataResultListener.onSuccess();
                }
            }

            @Override
            public void onError(int i) {
                getListAlarm().remove(alarm);
                if (onSaveDataResultListener != null) {
                    onSaveDataResultListener.onError(i);
                }
            }
        });
    }

    /**
     * This method is used to edit an melody and save it to HDD.
     *
     * @param newAlarm                 The model is saved to HDD.
     * @param onSaveDataResultListener The result listener when save data to hdd.
     */
    public void editAlarm(final AlarmData newAlarm, final OnSaveDataResultListener onSaveDataResultListener) {
        final AlarmData oldAlarm = getAlarmById(newAlarm.getId());
        final int index = getListAlarm().indexOf(oldAlarm);
        // Update old melody.
        getListAlarm().set(index, newAlarm);
        savePrefs(ConstantUtils.ALARM_PREFS_KEY, mAlarmPrefs, new OnPersistResultListener() {
            @Override
            public void onSuccess() {
                if (mOnDataChangedListener != null) {
                    mOnDataChangedListener.onAlarmChanged(newAlarm.getId());
                }
                if (onSaveDataResultListener != null) {
                    onSaveDataResultListener.onSuccess();
                }
            }

            @Override
            public void onError(int i) {
                getListAlarm().set(index, oldAlarm);
                if (onSaveDataResultListener != null) {
                    onSaveDataResultListener.onError(i);
                }
            }
        });
    }

    /**
     * This method is used to edit an melody and save it to HDD.
     *
     * @param enabled                  Status enable or disable of melody.
     * @param newAlarm                 The model is saved to HDD.
     * @param onSaveDataResultListener The result listener when save data to hdd.
     */
    public void enableAlarm(final boolean enabled, final AlarmData newAlarm, final OnSaveDataResultListener onSaveDataResultListener) {
        final int index = getListAlarm().indexOf(newAlarm);
        // Update old melody.
        getListAlarm().set(index, newAlarm);
        savePrefs(ConstantUtils.ALARM_PREFS_KEY, mAlarmPrefs, new OnPersistResultListener() {
            @Override
            public void onSuccess() {
                if (mOnDataChangedListener != null) {
                    mOnDataChangedListener.onAlarmChanged(newAlarm.getId());
                }
                if (onSaveDataResultListener != null) {
                    onSaveDataResultListener.onSuccess();
                }
            }

            @Override
            public void onError(int i) {
                newAlarm.setEnabled(!enabled);
                getListAlarm().set(index, newAlarm);
                if (mOnDataChangedListener != null) {
                    mOnDataChangedListener.onAlarmChanged(newAlarm.getId());
                }
                if (onSaveDataResultListener != null) {
                    onSaveDataResultListener.onError(i);
                }
            }
        });
    }

    /**
     * This method is used to delete an melody by id of melody.
     *
     * @param alarmData                The melody which is deleted.
     * @param onSaveDataResultListener The result listener when delete data.
     */
    public void deleteAlarm(final AlarmData alarmData, final OnSaveDataResultListener onSaveDataResultListener) {
        final int deletedAlarmPos = DBManager.getInstance().getListAlarm().indexOf(alarmData);
        getListAlarm().remove(alarmData);
        savePrefs(ConstantUtils.ALARM_PREFS_KEY, mAlarmPrefs, new OnPersistResultListener() {
            @Override
            public void onSuccess() {
                if (mOnDataChangedListener != null) {
                    mOnDataChangedListener.onAlarmRemoved(alarmData.getId());
                }
                if (onSaveDataResultListener != null) {
                    onSaveDataResultListener.onSuccess();
                }
            }

            @Override
            public void onError(int i) {
                getListAlarm().add(deletedAlarmPos, alarmData);
                if (onSaveDataResultListener != null) {
                    onSaveDataResultListener.onError(i);
                }
            }
        });
    }

    /**
     * Validate an melody data when add or edit.
     *
     * @param alarmData
     * @return false if data has the same day, same hour, same minute with item in list.
     * true if data does not have the same day or same hour or same minute with item in list.
     */
    public boolean validateAlarm(AlarmData alarmData) {
        boolean isValidAlarm = true;
        for (AlarmData itemList : mAlarmPrefs.getAlarmDataList()) {
            if (alarmData.getId() != itemList.getId()) {
                // Check hour and minute of melody are valid.
                if (alarmData.getHours() == itemList.getHours() && alarmData.getMinutes() == itemList.getMinutes()) {
                    // Check day of week of melody data has the same day with item in list.
                    for (int i = 0; i < alarmData.getDays().length; i++) {
                        for (int j = 0; j < itemList.getDays().length; j++) {
                            if (i == j && alarmData.getDays()[i] == true && itemList.getDays()[j] == true) {
                                isValidAlarm = false;
                                break;
                            }
                        }
                    }
                }
            }
        }
        return isValidAlarm;
    }

    private void savePrefs(String key, StorageFile storageFile, OnPersistResultListener onPersistResultListener) {
        if (mStorageRepository != null) {
            mStorageRepository.put(key, storageFile);
            try {
                mStorageRepository.persists(key, onPersistResultListener);
            } catch (IOException e) {
                LogUtils.e(TAG, e.getMessage());
            }
        }
    }
}
